---
id: dbcontext-proc
title: 9.13 存储过程操作
sidebar_label: 9.13 存储过程操作
---

:::important 温馨提示

推荐使用 《[9.17 Sql 高级代理](dbcontext-sql-proxy.mdx)》代替本章节功能。`Sql 高级代理` 能够提供更容易且更易维护的方式。

:::

import useBaseUrl from "@docusaurus/useBaseUrl";
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

## 9.13.1 关于存储过程

引用百度百科：

> 存储过程（Stored Procedure）是在大型数据库系统中，一组为了完成特定功能的 SQL 语句集，它存储在数据库中，一次编译后永久有效，用户通过指定存储过程的名字并给出参数（如果该存储过程带有参数）来执行它。
>
> 存储过程是数据库中的一个重要对象。在数据量特别庞大的情况下利用存储过程能达到倍速的效率提升。

简单来说，存储过程就是关系型数据库中（`Sqlite` 除外）中编写逻辑的函数/方法，通过这种方式，可以将 `sql` 编译缓存起来，大大提高存储过程的执行效率。

**这里不讨论存储过程的优缺点。**

### 9.13.1.1 支持存储过程的数据库

| SqlServer | Sqlite | Cosmos | InMemoryDatabase | MySql | PostgreSQL | Oracle | Firebird | Dm  |
| --------- | ------ | ------ | ---------------- | ----- | ---------- | ------ | -------- | --- |
| ✔         |        | ✔      |                  | ✔     | ✔          | ✔      | ✔        | ✔   |

## 9.13.2 存储过程使用

`Furion` 框架中提供了多种存储过程操作方式：

- `ISqlRepository`：`Sql` 操作仓储，可通过 `依赖注入`
- `ISqlDispatchProxy`：`Sql` 代理方式（高级用法，推荐）
- 通过任意实体仓储操作：`personRepository.SqlProcedureQuery(procName)`
- 通过字符串拓展方法：`procName.SqlProcedureQuery()`
- 通过 `repository.Sql().SqlProcedureQuery()` 方式

### 9.13.2.1 初始化方式

<Tabs
  defaultValue="one"
  values={[
    { label: "ISqlRepository", value: "one" },
    { label: "ISqlDispatchProxy", value: "two" },
    { label: "实体仓储方式", value: "three" },
    { label: "字符串拓展方式", value: "four" },
  ]}
>
  <TabItem value="one">

```cs {1,9-12}
using Furion.DatabaseAccessor;
using Furion.DynamicApiController;

namespace Furion.Application
{
    [DynamicApiController]
    public class SqlService
    {
        private ISqlRepository _sqlRepository;
        public SqlService(ISqlRepository sqlRepository)
        {
            _sqlRepository = sqlRepository;
        }
    }
}
```

  </TabItem>
  <TabItem value="two">

```cs {2,7,9-10}
using Furion.Core;
using Furion.DatabaseAccessor;
using System.Collections.Generic;

namespace Furion.Application
{
    public interface ISqlExecuteProxy : ISqlDispatchProxy
    {
        [SqlProcedure("proc_GetPersons")]
        List<Person> GetPersons(string keyword);
    }
}
```

```cs {9-12}
using Furion.DatabaseAccessor;
using Furion.DynamicApiController;

namespace Furion.Application
{
    [DynamicApiController]
    public class SqlService
    {
        private ISqlExecuteProxy _sqlExecuteProxy;
        public SqlService(ISqlExecuteProxy sqlExecuteProxy)
        {
            _sqlExecuteProxy = sqlExecuteProxy;
        }
    }
}
```

  </TabItem>
<TabItem value="three">

```cs
var persons = personRepository.SqlProcedureQuery("proc_GetPersons");
```

  </TabItem>
  <TabItem value="four">

```cs
var persons = "proc_GetPersons".SqlProcedureQuery();
```

  </TabItem>

</Tabs>

### 9.13.2.2 返回 `DataTable`

```cs
// ISqlRepository 方法
var dataTable = _sqlRepository.SqlProcedureQuery("proc_GetPersons");

// ISqlDispatchProxy 方式
var dataTable = _sqlExecuteProxy.GetPersons();  // 推荐方式

// 实体仓储方式
var dataTable = _personRepository.SqlProcedureQuery("proc_GetPersons");

// IRepository 非泛型方式
var dataTable = _repository.Sql().SqlProcedureQuery("proc_GetPersons");

// 变态懒人方式，直接通过存储过程名执行
var dataTable = "proc_GetPersons".SqlProcedureQuery();
```

:::note 关于异步

`Furion` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

### 9.13.2.3 返回 `DataSet`

```cs
// ISqlRepository 方法
var dataSet = _sqlRepository.SqlProcedureQueries("proc_GetPersons");

// ISqlDispatchProxy 方式
var dataSet = _sqlExecuteProxy.GetPersons();  // 推荐方式

// 实体仓储方式
var dataSet = _personRepository.SqlProcedureQueries("proc_GetPersons");

// IRepository 非泛型方式
var dataSet = _repository.Sql().SqlProcedureQueries("proc_GetPersons");

// 变态懒人方式，直接通过存储过程名执行
var dataSet = "proc_GetPersons".SqlProcedureQueries();
```

:::note 关于异步

`Furion` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

### 9.13.2.4 返回 `List<T>`

```cs
// ISqlRepository 方法
var list = _sqlRepository.SqlProcedureQuery<Person>("proc_GetPersons");

// ISqlDispatchProxy 方式
var list = _sqlExecuteProxy.GetPersons();  // 推荐方式

// 实体仓储方式
var list = _personRepository.SqlProcedureQuery<Person>("proc_GetPersons");

// IRepository 非泛型方式
var list = _repository.Sql().SqlProcedureQuery<Person>("proc_GetPersons");

// 变态懒人方式，直接通过存储过程名执行
var list = "proc_GetPersons".SqlProcedureQuery<Person>();
```

:::note 关于异步

`Furion` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

### 9.13.2.5 返回 `Tuple<T1,...T8>`

`Furion` 框架大大利用了 `Tuple` 的特性，将返回多个结果集转成 `Tuple<T1,...T8>` 类型

```cs
// ISqlRepository 方法

// 返回一个结果集
var list1 = _sqlRepository.SqlProcedureQueries<T1>("proc_GetPersons");

// 返回两个结果集
var (list1, list2) = _sqlRepository.SqlProcedureQueries<T1,T2>("proc_GetPersons");

// 返回三个结果集
var (list1, list2, list3) = _sqlRepository.SqlProcedureQueries<T1,T2,T3>("proc_GetPersons");

// 返回四个结果集
var (list1, list2, list3, list4) = _sqlRepository.SqlProcedureQueries<T1,T2,T3,T4>("proc_GetPersons");

// 返回五个结果集
var (list1, list2, list3, list4, list5) = _sqlRepository.SqlProcedureQueries<T1,T2,T3,T4,T5>("proc_GetPersons");

// 返回六个结果集
var (list1, list2, list3, list4, list5, list6) = _sqlRepository.SqlProcedureQueries<T1,T2,T3,T4,T5,T6>("proc_GetPersons");

// 返回七个结果集
var (list1, list2, list3, list4, list5, list6, list7) = _sqlRepository.SqlProcedureQueries<T1,T2,T3,T4,T5,T6,T7>("proc_GetPersons");

// 返回八个结果集
var (list1, list2, list3, list4, list5, list6, list7, list8) = _sqlRepository.SqlProcedureQueries<T1,T2,T3,T4,T5,T6,T7,T8>("proc_GetPersons");

// ==================================

// ISqlDispatchProxy 方式，推荐方式，自动处理多个结果集

// 返回一个结果集
var list1 = _sqlRepository.GetDatas();

// 返回两个结果集
var (list1, list2) = _sqlRepository.GetDatas();

// 返回三个结果集
var (list1, list2, list3) = _sqlRepository.GetDatas();

// 返回四个结果集
var (list1, list2, list3, list4) = _sqlRepository.GetDatas();

// 返回五个结果集
var (list1, list2, list3, list4, list5) = _sqlRepository.GetDatas();

// 返回六个结果集
var (list1, list2, list3, list4, list5, list6) = _sqlRepository.GetDatas();

// 返回七个结果集
var (list1, list2, list3, list4, list5, list6, list7) = _sqlRepository.GetDatas();

// 返回八个结果集
var (list1, list2, list3, list4, list5, list6, list7, list8) = _sqlRepository.GetDatas();

// ==================================

// 实体仓储方式

// 返回一个结果集
var list1 = _personRepository.SqlProcedureQueries<T1>("proc_GetPersons");

// 返回两个结果集
var (list1, list2) = _personRepository.SqlProcedureQueries<T1,T2>("proc_GetPersons");

// 返回三个结果集
var (list1, list2, list3) = _personRepository.SqlProcedureQueries<T1,T2,T3>("proc_GetPersons");

// 返回四个结果集
var (list1, list2, list3, list4) = _personRepository.SqlProcedureQueries<T1,T2,T3,T4>("proc_GetPersons");

// 返回五个结果集
var (list1, list2, list3, list4, list5) = _personRepository.SqlProcedureQueries<T1,T2,T3,T4,T5>("proc_GetPersons");

// 返回六个结果集
var (list1, list2, list3, list4, list5, list6) = _personRepository.SqlProcedureQueries<T1,T2,T3,T4,T5,T6>("proc_GetPersons");

// 返回七个结果集
var (list1, list2, list3, list4, list5, list6, list7) = _personRepository.SqlProcedureQueries<T1,T2,T3,T4,T5,T6,T7>("proc_GetPersons");

// 返回八个结果集
var (list1, list2, list3, list4, list5, list6, list7, list8) = _personRepository.SqlProcedureQueries<T1,T2,T3,T4,T5,T6,T7,T8>("proc_GetPersons");

// ==================================

// IRepository 非泛型方式

// 返回一个结果集
var list1 = _repository.Sql().SqlProcedureQueries<T1>("proc_GetPersons");

// 返回两个结果集
var (list1, list2) = _repository.Sql().SqlProcedureQueries<T1,T2>("proc_GetPersons");

// 返回三个结果集
var (list1, list2, list3) = _repository.Sql().SqlProcedureQueries<T1,T2,T3>("proc_GetPersons");

// 返回四个结果集
var (list1, list2, list3, list4) = _repository.Sql().SqlProcedureQueries<T1,T2,T3,T4>("proc_GetPersons");

// 返回五个结果集
var (list1, list2, list3, list4, list5) = _repository.Sql().SqlProcedureQueries<T1,T2,T3,T4,T5>("proc_GetPersons");

// 返回六个结果集
var (list1, list2, list3, list4, list5, list6) = _repository.Sql().SqlProcedureQueries<T1,T2,T3,T4,T5,T6>("proc_GetPersons");

// 返回七个结果集
var (list1, list2, list3, list4, list5, list6, list7) = _repository.Sql().SqlProcedureQueries<T1,T2,T3,T4,T5,T6,T7>("proc_GetPersons");

// 返回八个结果集
var (list1, list2, list3, list4, list5, list6, list7, list8) = _repository.Sql().SqlProcedureQueries<T1,T2,T3,T4,T5,T6,T7,T8>("proc_GetPersons");

// ==================================

// 变态懒人方式，直接通过存储过程名执行

// 返回一个结果集
var list1 = "proc_GetPersons".SqlProcedureQueries<T1>();

// 返回两个结果集
var (list1, list2) = "proc_GetPersons".SqlProcedureQueries<T1,T2>();

// 返回三个结果集
var (list1, list2, list3) = "proc_GetPersons".SqlProcedureQueries<T1,T2,T3>();

// 返回四个结果集
var (list1, list2, list3, list4) = "proc_GetPersons".SqlProcedureQueries<T1,T2,T3,T4>();

// 返回五个结果集
var (list1, list2, list3, list4, list5) = "proc_GetPersons".SqlProcedureQueries<T1,T2,T3,T4,T5>();

// 返回六个结果集
var (list1, list2, list3, list4, list5, list6) = "proc_GetPersons".SqlProcedureQueries<T1,T2,T3,T4,T5,T6>();

// 返回七个结果集
var (list1, list2, list3, list4, list5, list6, list7) = "proc_GetPersons".SqlProcedureQueries<T1,T2,T3,T4,T5,T6,T7>();

// 返回八个结果集
var (list1, list2, list3, list4, list5, list6, list7, list8) = "proc_GetPersons".SqlProcedureQueries<T1,T2,T3,T4,T5,T6,T7,T8>();
```

:::note 关于异步

`Furion` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

### 9.13.2.6 返回 `单行单列 object`

```cs
// ISqlRepository 方法
var value = _sqlRepository.SqlProcedureScalar("proc_GetName");

// ISqlDispatchProxy 方式
var value = _sqlExecuteProxy.GetName();  // 推荐方式

// 实体仓储方式
var value = _personRepository.SqlProcedureScalar("proc_GetName");

// IRepository 非泛型方式
var value = _repository.Sql().SqlProcedureScalar("proc_GetName");

// 变态懒人方式，直接通过存储过程名执行
var value = "proc_GetName".SqlProcedureScalar();
```

:::note 关于异步

`Furion` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

### 9.13.2.7 返回 `单行单列 <T>`

```cs
// ISqlRepository 方法
var value = _sqlRepository.SqlProcedureScalar<string>("proc_GetName");

// ISqlDispatchProxy 方式
var value = _sqlExecuteProxy.GetName();  // 推荐方式

// 实体仓储方式
var value = _personRepository.SqlProcedureScalar<string>("proc_GetName");

// IRepository 非泛型方式
var value = _repository.Sql().SqlProcedureScalar<string>("proc_GetName");

// 变态懒人方式，直接通过存储过程名执行
var value = "proc_GetName".SqlProcedureScalar<string>();
```

:::note 关于异步

`Furion` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

### 9.13.2.8 执行但无返回

```cs
// ISqlRepository 方法
_sqlRepository.SqlProcedureNonQuery("proc_UpdateData");

// ISqlDispatchProxy 方式
_sqlExecuteProxy.UpdateData();  // 推荐方式

// 实体仓储方式
_personRepository.SqlProcedureNonQuery("proc_UpdateData");

// IRepository 非泛型方式
_repository.Sql().SqlProcedureNonQuery("proc_UpdateData");

// 变态懒人方式，直接通过存储过程名执行
"proc_UpdateData".SqlProcedureNonQuery();
```

:::note 关于异步

`Furion` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

## 9.13.3 执行复杂存储过程

在存储过程中，有一种例子非常复杂，那就是既有 `INPUT` 参数，又有 `OUTPUT` 参数，还有 `RETURN` 参数，同时还输出 结果集 💥，如：

```sql {3,4,10-12,15-17,22}
CREATE PROC PROC_Output
    @Id INT,    // 输入参数
    @Name NVARCHAR(32) OUTPUT,  // 输出参数，还带长度
    @Age INT OUTPUT // 输出参数
AS
BEGIN
    SET @Name = 'Furion Output';

    // 输出结果集
    SELECT *
    FROM dbo.Test
    WHERE Id > @Id;

    // 输出结果集
    SELECT TOP 10
           *
    FROM dbo.Test;

    SET @Age = 27;

    // 带 RETURN 返回
    RETURN 10;
END;
```

### 9.13.3.1 创建参数模型

```cs {1,10,13,16}
using Furion.DatabaseAccessor;
using System.Data;

namespace Furion.Application
{
    public class ProcOutputModel
    {
        public int Id { get; set; } // 输入参数

        [DbParameter(ParameterDirection.Output, Size = 32)]
        public string Name { get; set; }    // 输出参数

        [DbParameter(ParameterDirection.Output)]
        public int Age { get; set; }    // 输出参数

        [DbParameter(ParameterDirection.ReturnValue)]
        public int ReturnValue { get; set; }    // 返回值
    }
}
```

### 9.13.3.2 执行复杂存储过程

- ** `DataSet` ** 方式

```cs
// ISqlRepository 方法
ProcedureOutputResult result = _sqlRepository.SqlProcedureOutput("proc_Complex", new ProcOutputModel{});

// ISqlDispatchProxy 方式
ProcedureOutputResult result = _sqlExecuteProxy.Complex(new ProcOutputModel{});  // 推荐方式

// 实体仓储方式
ProcedureOutputResult result = _personRepository.SqlProcedureOutput("proc_Complex", new ProcOutputModel{});

// IRepository 非泛型方式
ProcedureOutputResult result = _repository.Sql().SqlProcedureOutput("proc_Complex", new ProcOutputModel{});

// 变态懒人方式，直接通过存储过程名执行
ProcedureOutputResult result = "proc_Complex".SqlProcedureOutput(new ProcOutputModel{});
```

```cs
// 获取 OUTPUT 参数值
var outputs = result.OutputValues;

// 获取 RETURN 返回值
var reval = result.ReturnValue;

// 获取返回结果集
var dataSet = result.Result;
```

- `Tuple<T1,...T8>` 方式

```cs
// ISqlRepository 方法
ProcedureOutputResult<(List<Test>, List<Test>)> result = _sqlRepository.SqlProcedureOutput<(List<Test>, List<Test>)>("proc_Complex", new ProcOutputModel{});

// ISqlDispatchProxy 方式
ProcedureOutputResult<(List<Test>, List<Test>)> result = _sqlExecuteProxy.Complex(new ProcOutputModel{});  // 推荐方式

// 实体仓储方式
ProcedureOutputResult<(List<Test>, List<Test>)> result = _personRepository.SqlProcedureOutput<(List<Test>, List<Test>)>("proc_Complex", new ProcOutputModel{});

// IRepository 非泛型方式
ProcedureOutputResult<(List<Test>, List<Test>)> result = _repository.Sql().SqlProcedureOutput<(List<Test>, List<Test>)>("proc_Complex", new ProcOutputModel{});

// 变态懒人方式，直接通过存储过程名执行
ProcedureOutputResult<(List<Test>, List<Test>)> result = "proc_Complex".SqlProcedureOutput<(List<Test>, List<Test>)>(new ProcOutputModel{});
```

```cs
// 获取 OUTPUT 参数值
var outputs = result.OutputValues;

// 获取 RETURN 返回值
var reval = result.ReturnValue;

// 获取返回结果集
var (list1,list2) = result.Result;
```

:::note 关于异步

`Furion` 框架每一个数据库操作都支持异步方式，由于篇幅有限，就不列举异步方式了。

:::

## 9.13.3 关于 `[DbParameter]`

`[DbParameter]` 特性是用来标注 `Sql`，`函数`，`存储过程` 参数的，可配置属性：

- `Direction`：设置参数方向，`ParameterDirection` 枚举类型，默认 `ParameterDirection.Input`
- `DbType`：设置参数类型，`DbType` 枚举类型，无默认
- `Size`：设置参数长度的，`int` 类型

其中 `Direction` 属性是默认构造函数参数。

## 9.13.4 关于 `ProcedureOutputResult`

`ProcedureOutputResult` 和 `ProcedureOutputResult<TResult>` 是复杂存储过程执行返回模型类，有以下属性：

- `OutputValues`：多个输出值，`ProcedureOutputValue` 类型
- `ReturnValue`：返回值，`object` 类型
- `Result`：结果集，非泛型版本是 `DataSet`类型，否则是 泛型类型

## 9.13.5 存储过程参数

所有 `sql` 参数都支持四种方式：

- `DbParameter[]`：数组类型
- `new {}`：匿名类型
- `new Class{}`：强类型类型（支持复杂存储过程参数）
- `Dictionary<string,object>` 类型

## 9.13.6 反馈与建议

:::note 与我们交流

给 Furion 提 [Issue](https://gitee.com/dotnetchina/Furion/issues/new?issue)。

:::
